/*
 * The MIT License
 *
 * Copyright 2012 http://generic-toolset.googlecode.com.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.rlpw.swing;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import org.rlpw.util.UniqueList;

class ListSelectionAdapter<T> extends DefaultSelectModel<T> implements ListSelectionModel {

    private boolean isAdjusting = false;
    private int anchorIndex = -1;
    private int leadIndex = -1;
    private List<ListSelectionListener> listeners = new UniqueList<>();
    private int oldIndex0 = -1, oldIndex1 = -1;
    private State oldState;

    public ListSelectionAdapter(View<T> view) {
        super(view);
    }

    @Override
    public void setSelectionInterval(int index0, int index1) {
        rangeCheck(index0);
        rangeCheck(index1);
        setAllState(false);
        intervalSelected(index0, index1, true);
    }

    @Override
    public void addSelectionInterval(int index0, int index1) {
        rangeCheck(index0);
        rangeCheck(index1);
        intervalSelected(index0, index1, true);
    }

    @Override
    public void removeSelectionInterval(int index0, int index1) {
        try {
            intervalSelected(index0, index1, false);
        } catch (Exception excep) {
        }
    }

    private void fireValueChanged() {
        List<Integer> turningIndices = new LinkedList<>();
        turningIndices.add(oldIndex0);
        turningIndices.add(oldIndex1);

        turningIndices.add(oldIndex0 = getMinSelectionIndex());
        turningIndices.add(oldIndex1 = getMaxSelectionIndex());

        int min = Math.max(0, Collections.min(turningIndices));
        int max = Collections.max(turningIndices);

        ListSelectionEvent event = new ListSelectionEvent(this, min, max, isAdjusting);
        State state = new State();

        if (oldState == null || !state.equals(oldState)) {
            for (ListSelectionListener l : listeners) {
                l.valueChanged(event);
            }

        }
        oldState = state;
    }

    private void intervalSelected(int index0, int index1, boolean state) {
        List<T> visibleItems = getView().getVisibleItems();
        anchorIndex = index0;
        leadIndex = index1;
        for (T item : visibleItems.subList(
                Math.min(index0, index1),
                Math.max(index0, index1) + 1)) {
            setState(item, state);
        }
        fireValueChanged();
    }

    @Override
    public int getMinSelectionIndex() {
        List<Integer> indices = getSelectedIndices();
        if (indices.isEmpty()) {
            return -1;
        } else {
            return Collections.min(indices);
        }
    }

    @Override
    public int getMaxSelectionIndex() {
        List<Integer> indices = getSelectedIndices();
        if (indices.isEmpty()) {
            return -1;
        } else {
            return Collections.max(indices);
        }
    }

    private List<Integer> getSelectedIndices() {
        List<Integer> indices = new LinkedList<>();
        List<T> visibleItems = getView().getVisibleItems();
        for (T item : getSelected()) {
            indices.add(visibleItems.indexOf(item));
        }

        return indices;
    }

    @Override
    public boolean isSelectedIndex(int index) {
        return isSelected(index);
    }

    @Override
    public int getAnchorSelectionIndex() {
        return anchorIndex;
    }

    @Override
    public void setAnchorSelectionIndex(int index) {
        anchorIndex = index;
    }

    @Override
    public int getLeadSelectionIndex() {
        return leadIndex;
    }

    @Override
    public void setLeadSelectionIndex(int index) {
        leadIndex = index;
    }

    @Override
    public void clearSelection() {
        setAllState(false);
    }

    @Override
    public boolean isSelectionEmpty() {
        return getSelected().isEmpty();
    }

    @Override
    public void insertIndexInterval(int index, int length, boolean before) {
    }

    @Override
    public void removeIndexInterval(int index0, int index1) {
    }

    @Override
    public void setValueIsAdjusting(boolean isAdjusting) {
        this.isAdjusting = isAdjusting;
    }

    @Override
    public boolean getValueIsAdjusting() {
        return isAdjusting;
    }

    @Override
    public void setSelectionMode(int selectionMode) {
        setMode(selectionMode);
    }

    @Override
    public int getSelectionMode() {
        return getMode();
    }

    @Override
    public void addListSelectionListener(ListSelectionListener listener) {
        if (listeners != null) {
            listeners.add(listener);
        }
    }

    @Override
    public void removeListSelectionListener(ListSelectionListener listener) {
        listeners.remove(listener);
    }

    @Override
    public void setSelected(int index, boolean select) {
        setState(index, select);
        fireValueChanged();
    }

    @Override
    public void setAllSelected(boolean select) {
        setAllState(select);
        fireValueChanged();
    }

    private void rangeCheck(int index) {
        if (index < 0 || index >= getView().getVisibleItems().size()) {
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + getView().getVisibleItems().size());
        }
    }

    private class State {

        private List<T> selected;

        public State() {
            selected = ListSelectionAdapter.this.getSelected();
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final State other = (State) obj;
            if (other.getSelected().size() != getSelected().size()) {
                return false;
            }
            for (T thisItem : getSelected()) {
                for (T otherItem : other.getSelected()) {
                    if (!thisItem.equals(otherItem)) {
                        return false;
                    }
                }
            }

            return true;
        }

        List<T> getSelected() {
            return selected;
        }
    }
}
